﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Space_Conquest
{
    abstract class MovableUnit : Unit
    {

        #region Constructor

        public MovableUnit(Texture texture, int x, int y, Player owner) :
            base(texture, x, y, owner)
        {

            _destPoint.X = x;
            _destPoint.Y = y;

        } 

        #endregion

        #region Private Members

        protected UnitDirection _direction = UnitDirection.Right;
        protected Stack<UnitState> _state = new Stack<UnitState>();
       
        protected int _attackDamage;
        protected int _attackRange;
        protected int _attackDelay;
        protected int _defense;
        protected int _speed;

        protected Unit _unitToAttack;
        protected MapPoint _destPoint = new MapPoint(0, 0);
        protected int _stepDistance;
        protected int _currentAttackDelay;

        List<UnitDirection> _path = new List<UnitDirection>();

        #endregion

        #region Properties

        public int AttackDamage
        {
            get
            {
                return _attackDamage;
            }
        }

        public int AttackRange
        {
            get
            {
                return _attackRange;
            }
        }

        public int AttackDelay
        {
            get
            {
                return _attackDelay;
            }
        }

        public int Defense
        {
            get
            {
                return _defense;
            }
        }

        public int Speed
        {
            get
            {
                return _speed;
            }
        }

        public bool IsIdle
        {
            get
            {
                return (_state.Count == 0);
            }
        }

        #endregion

        #region Methods

        public override void Motion()
        {

            bool tookAction = false;

            // Attack delay can decrease anytime
            if (_currentAttackDelay > 0)
                --_currentAttackDelay;

            // Only continue if Unit has something to do
            while (!_state.UnitIsStill() && !tookAction)
            {

                switch (_state.Peek())
                {
                    case UnitState.Attack:
                        if (_unitToAttack.IsAlive)
                        {
                            if (_unitToAttack.MapArea.IsAdjacentTo(MapArea))
                            {
                                // In range of unit, attack it!
                                _state.Push(UnitState.Attacking);

                            }
                            else
                            {
                                // Need to move closer before attacking
                                
                                _destPoint = _unitToAttack.GetClosestAdjacentAvailablePoint(MapPoint);
                                if (_destPoint == null)
                                    _destPoint = _unitToAttack.MapArea.GetClosestAdjacentPoint(MapPoint);
                                
                                if(Parent.Parent.Map.TileIsPassable(_layer, _destPoint))
                                {

                                    _state.Push(UnitState.MoveToTile);
                                
                                }
                                else
                                {

                                    Paralyze(10);

                                }

                            }
                        }
                        else
                        {
                            _state.Pop();
                        }
                        break;
                    case UnitState.Attacking:
                        if (_unitToAttack.IsAlive && _unitToAttack.MapArea.IsAdjacentTo(MapArea))
                        {
                            if (_currentAttackDelay == 0)
                            {

                                Parent.Parent.LocalPlayer.PlaySound("Sounds/SmallHit.wav", _unitToAttack.MapPoint);

                                // Attack
                                _unitToAttack.TakeHit(_attackDamage, this);

                                // Reset delay
                                _currentAttackDelay = _attackDelay;

                            }

                            tookAction = true;
                        }
                        else
                        {
                            _state.Pop();
                        }
                        break;
                    case UnitState.MoveToTile:

                        if (_path.Count == 0 && (!MapPoint.Equals(_destPoint)))
                        {

                            PathFinder pathFinder = new PathFinder(Parent.Parent.Map, _layer);
                            List<UnitDirection> tempPath = pathFinder.FindPath(MapPoint, _destPoint);

                            if (tempPath == null)
                            {
                                // No possible paths. Quitting.
                                _state.Pop();
                            }
                            else
                            {
                                // Found Path, now settings
                                _path = tempPath;
                            }

                        }
                        else if (_path.Count == 0)
                        {
                            // Finished traveling path, quit.
                            _state.Pop();
                        }
                        else
                        {

                            if (TryMoveStep(_path[0]))
                            {
                                // Worked, continue

                                _stepDistance = Globals.TILE_SIZE;
                                _state.Push(UnitState.MoveStep);

                                _path.RemoveAt(0);

                            }
                            else
                            {
                                // Didn't work, need a new path

                                _path.Clear();
                            }

                        }


                        break;
                    case UnitState.MoveStep:

                        // Check if unit actually needs to move then entire _speed to get to it's destination
                        int checkDistance = (_stepDistance - _speed >= 0) ? _speed : _stepDistance ;
                        _stepDistance -= checkDistance;

                        if (_direction == UnitDirection.Down)
                            _sprite.Top += checkDistance;
                        else if (_direction == UnitDirection.Up)
                            _sprite.Top -= checkDistance;
                        else if (_direction == UnitDirection.Left)
                            _sprite.Left -= checkDistance;
                        else if (_direction == UnitDirection.Right)
                            _sprite.Left += checkDistance;

                        if (_stepDistance == 0)
                            _state.Pop();

                        tookAction = true;

                        break;
                    case UnitState.Paralyzed:

                        // Stops for one tick
                        _state.Pop();
                        tookAction = true;

                        break;
                    default:
                        tookAction = true;
                        break;
                }

            }

        }

        public override void Render()
        {

            if (_state.UnitIsStill())
            {
                _sprite.Render(_texture, (int)_direction);
            }
            else if (_state.UnitIsMoving())
            {
                _sprite.Render(_texture, (int)_direction + 4);
            }
            else
            {
                _sprite.Render(_texture, (int)_direction);
            }

            RenderLifeBar();

            if (this is Laborer)
                ((Laborer)this).RenderResourceBar();

        }

        public override void TakeHit(int damage)
        {

            int actualDamage = damage - _defense;
            
            if (actualDamage < 0)
                actualDamage = 0;

            base.TakeHit(actualDamage);



        }

        public override void TakeHit(int damage, Unit from)
        {

            TakeHit(damage);

            // Fight back if not doing anything
            if (_state.UnitIsStill())
            {
                Attack(from);
                Paralyze(5);
            }

        }

        public virtual void Move(int DestinationX, int DestinationY)
        {

            _destPoint.X = DestinationX;
            _destPoint.Y = DestinationY;

            ClearAndResetState(UnitState.MoveToTile);

        }

        public virtual void Attack(Unit unitToAttack)
        {

            _unitToAttack = unitToAttack;
            ClearAndResetState(UnitState.Attack);

        }

        protected void ClearAndResetState(UnitState newState)
        {
            if (_state.Count > 0 && _state.Peek() == UnitState.MoveStep)
            {
                _state.Clear();
                _path.Clear();
                _state.Push(newState);
                _state.Push(UnitState.MoveStep);
            }
            else
            {
                _state.Clear();
                _state.Push(newState);
            }
        }

        bool AdHocFindPath(UnitDirection direction)
        {

            if (TryMoveStep(direction))
            {
                // Can move to next point
                return true;
            }
            else
            {
                // Make list of all possible directions
                List<UnitDirection> possibleDirections = new List<UnitDirection>();
                possibleDirections.AddRange(new UnitDirection[] {UnitDirection.Down,
                                                                 UnitDirection.Left,
                                                                 UnitDirection.Up,
                                                                 UnitDirection.Right});

                // Take out the one we just tried, since it failed
                possibleDirections.Remove(direction);

                while (possibleDirections.Count > 0)
                {

                    int randomDirection = new Random().Next(0, possibleDirections.Count);

                    if (TryMoveStep(possibleDirections[randomDirection]))
                        return true;
                    else
                        possibleDirections.RemoveAt(randomDirection);

                }

                return false;

            }

        }

        bool TryMoveStep(UnitDirection direction)
        {

            _direction = direction;

            int currentX = _area.X;
            int currentY = _area.Y;

            if (direction == UnitDirection.Left)
            {
                if (TrySetLocation(_layer, _area.X - 1, _area.Y))
                {
                    UnsetLocation(_layer, currentX, currentY);
                    return true;
                }

            }
            else if (direction == UnitDirection.Right)
            {
                if (TrySetLocation(_layer, _area.X + 1, _area.Y))
                {
                    UnsetLocation(_layer, currentX, currentY);
                    return true;
                }
            }
            else if (direction == UnitDirection.Up)
            {
                if (TrySetLocation(_layer, _area.X, _area.Y - 1))
                {
                    UnsetLocation(_layer, currentX, currentY);
                    return true;
                }
            }
            else if (direction == UnitDirection.Down)
            {
                if (TrySetLocation(_layer, _area.X, _area.Y + 1))
                {
                    UnsetLocation(_layer, currentX, currentY);
                    return true;
                }
            }

            return false;

        }

        MapPoint GetRelativePoint(UnitDirection direction)
        {

            switch (direction)
            {
                case UnitDirection.Right:
                    return new MapPoint(MapPoint.X + 1, MapPoint.Y);
                case UnitDirection.Left:
                    return new MapPoint(MapPoint.X - 1, MapPoint.Y);
                case UnitDirection.Up:
                    return new MapPoint(MapPoint.X, MapPoint.Y - 1);
                case UnitDirection.Down:
                    return new MapPoint(MapPoint.X, MapPoint.Y + 1);
                default:
                    return null;
            }

        }

        public void Paralyze(int motionTicks)
        {
            for (int i = 0; i < motionTicks; ++i)
                _state.Push(UnitState.Paralyzed);
        }

        #endregion


    }
}
